package com.hrpc;

import com.hrpc.annotation.hRpcApi;
import com.hrpc.channelHandler.handler.MethodCallHandler;
import com.hrpc.channelHandler.handler.hRpcRequestDecoder;
import com.hrpc.channelHandler.handler.hRpcResponseEncoder;
import com.hrpc.config.Configuration;
import com.hrpc.core.HeartbeatDetector;
import com.hrpc.core.hRpcShutdownHook;
import com.hrpc.discovery.Register;
import com.hrpc.discovery.RegisterConfig;
import com.hrpc.loadbalancer.LoadBalancer;
import com.hrpc.transport.message.hRpcRequest;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import org.apache.zookeeper.ZooKeeper;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.net.URL;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

public class hRpcBootStarp {

    //hRpcBootStarp是个单例，我们希望每个应用程序只有一个实例
    private static final hRpcBootStarp hRpcBootStarp=new hRpcBootStarp();
    //全局的配置中心
    private Configuration configuration;

    //连接的缓存，如果使用这样的类做key一定要看他有没有重写equals和toString方法
    public static final Map<InetSocketAddress, Channel> CHANNEL_CACHE=new ConcurrentHashMap<>(16);
    public static final TreeMap<Long, Channel> ANSWER_TIME_CHANNEL_CACHE=new TreeMap<>();
    //维护一个zookeeper实例
    private ZooKeeper zooKeeper;
    //维护已经发布且暴露的服务列表key:interface的全限定名 value:ServiceConfig
    public static final Map<String,ServiceConfig<?>> SERVERS_LIST=new HashMap<>(16);
    //定义全局对外挂起的completableFuture
    public static final Map<Long, CompletableFuture<Object>> PENDING_REQUEST=new ConcurrentHashMap<>(128);

    public static final ThreadLocal<hRpcRequest> REQUEST_THREAD_LOCAL=new ThreadLocal<>();
    private hRpcBootStarp() {
        //构造启动引导程序时，需要做一些什么初始化的事情
        configuration = new Configuration();

    }

    public static hRpcBootStarp getInstance() {
        return hRpcBootStarp;
    }

    /**
     * 用来定义当前应用的名字
     * @param appName
     * @return
     */
    public hRpcBootStarp application(String appName) {
        configuration.setAppName(appName);
        return this;
    }

    /**
     * 用来配置一个注册中心
     * @return
     */
    public hRpcBootStarp register(RegisterConfig registerConfig) {
        // TODO 这里维护一个zookeeper实例，但是如果这样写就会将zookeeper和当前工程耦合
        //  我们希望以后能够扩展更多种不同实现
        configuration.setRegisterConfig(registerConfig);
        return this;
    }

    /**
     * 配置负载均衡策略
     * @param loadBalancer
     * @return
     */
    public hRpcBootStarp loadBalancer(LoadBalancer loadBalancer) {
        // TODO 这里维护一个zookeeper实例，但是如果这样写就会将zookeeper和当前工程耦合
        //  我们希望以后能够扩展更多种不同实现
        configuration.setLoad_balancer(loadBalancer);
        return this;
    }


    /**
     * 服务的核心是将接口和实现类注册到服务器中心
     *
     * 服务的发布
     * @param service 封装的需要发布的服务
     * @return
     */
    public hRpcBootStarp publish(ServiceConfig<?> service) {
        //我们抽象了注册中心的概念，使用注册中心的一个实现完成注册
        //有人会想此时此刻
        configuration.getRegisterConfig().getRegister().register(service);
        //1.当服务调用方，通过接口、方法名、具体的方法参数列表发起调用提供方怎么知道使用哪一个实现
        // (1)new 一个 (2)spring beanFactory.getBean(class) (3)尝试自己维护映射关系
        SERVERS_LIST.put(service.getInterfaceProvider().getName(),service);
        return this;
    }

    /**
     * 批量服务的发布
     * @param services 封装的需要发布的服务
     * @return
     */
    public hRpcBootStarp publish(List<ServiceConfig<?>> services) {
        services.forEach(serviceConfig -> {
            this.publish(serviceConfig);
        });
        return this;
    }

    /**
     * 启动netty服务
     */
    public void start() {
        //优先注册一个关闭应用程序的钩子函数
        Runtime.getRuntime().addShutdownHook(new hRpcShutdownHook());

        //1.创建eventLoop，老板只负责处理请求，之后会将请求分发给worker
        EventLoopGroup boss=new NioEventLoopGroup(2);
        EventLoopGroup worker=new NioEventLoopGroup(10);
        try {
            //2，需要一个服务器引导程序
            ServerBootstrap serverBootstrap=new ServerBootstrap();
            //3.配置服务器
            serverBootstrap = serverBootstrap.group(boss,worker)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            //这里是核心，需要添加很多入栈和出栈的handler，添加一个服务器
                            socketChannel.pipeline().addLast(new LoggingHandler())
                                    .addLast(new hRpcRequestDecoder())
                                    //根据请求进行方法调用
                                    .addLast(new MethodCallHandler())
                                    //给响应进行编码
                                    .addLast(new hRpcResponseEncoder());
                        }
                    });
            //4.绑定端口
            //一定要同步，拿到channelFuture
            ChannelFuture channelFuture = serverBootstrap.bind(configuration.getPort()).sync();
            //同步进行阻塞住了，可以进行处理一些事情。
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            try {
                boss.shutdownGracefully().sync();
                worker.shutdownGracefully().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * -------------------------调用方API-------------------------------------
     */
    /**
     *
     * @param referenceConfig
     */
    public hRpcBootStarp reference(ReferenceConfig<?> referenceConfig) {
        //开启对这个服务的心跳检测
        HeartbeatDetector.detectHeart(referenceConfig.getInterfaceRef().getName());

        //在这个方法里我们是否可以拿到相关的配置项 - 注册中心
        //配置reference，将来调用get方法时，方便得到代理对象
        //1.referenceConfig需要一个注册中心
        referenceConfig.setRegister(configuration.getRegisterConfig().getRegister());
        referenceConfig.setGroup(this.getConfiguration().getGroup());
        return this;
    }

    /**
     * 配置序列化的方式
     * @param serialize
     * @return
     */
    public hRpcBootStarp serialize(String serialize) {
        configuration.setSerializeType(serialize);
        return this;
    }
    public hRpcBootStarp compress(String compress){
        configuration.setCompressType(compress);
        return this;
    }

    public Register getRegister() {
        return configuration.getRegisterConfig().getRegister();
    }

    /**
     * 扫包批量发布
     * @param packageName
     * @return
     */
    public hRpcBootStarp scan(String packageName){
        //1.需要通过packageName获取其下的所有类的权限定名称
        List<String> classNames = getAllClassName(packageName);
        //2.通过反射获取它的接口，构建具体实现
        List<Class<?>> classes = classNames.stream()
                .map(className -> {
                    try {
                        return Class.forName(className);
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                })
                .filter(clazz -> clazz.getAnnotation(hRpcApi.class) != null)
                .collect(Collectors.toList());
        for (Class<?> aClass : classes) {
            //1.获取接口，
            Class<?>[] interfaces = aClass.getInterfaces();
            Object instance=null;
            try {
                instance = aClass.getConstructor().newInstance();
            } catch (InstantiationException | IllegalAccessException | InvocationTargetException |
                     NoSuchMethodException e) {
                throw new RuntimeException(e);
            }
            //获取分组信息
            hRpcApi hRpcApi = aClass.getAnnotation(hRpcApi.class);
            String group = hRpcApi.group();
            //对接口进行遍历，因为一个实现类可以实现多个接口
            for (Class<?> anInterface : interfaces) {
                ServiceConfig<?> serviceConfig=new ServiceConfig<>();
                serviceConfig.setInterfaceProvider(anInterface);
                serviceConfig.setReference(instance);
                serviceConfig.setGroup(group);
                //一个一个发布
                publish(serviceConfig);
            }
        }
        return this;
    }

    private List<String> getAllClassName(String packageName) {
        //1,通过传入的packageName获得绝对路径，-------------(传入的是正则表达式.代表所有字符，想让.代表.需要转义符\\，\\代表转义符，\\代表斜杠)
        String basePath = packageName.replaceAll("\\.","/");
        URL url = ClassLoader.getSystemClassLoader().getResource(basePath);
        if (url == null){
            throw new RuntimeException("包扫描时路径出现异常！！！");
        }
        String path = url.getPath();
        List<String> classNames=new ArrayList<>();
        //classNames 存所有类的全限定名称
        classNames = recusionFile(path,classNames,basePath);
        return classNames;
    }

    private List<String> recusionFile(String path, List<String> classNames,String basePath) {
        //1.获取文件
        File file=new File(path);
        //2.判断文件是否是文件夹
        if (file.isDirectory()){
            //找到文件夹的所有文件，只要文件夹或者后缀为.class的资源
            File[] childFiles = file.listFiles(pathname -> pathname.isDirectory() || pathname.getPath().contains(".class"));
            if (childFiles ==null || childFiles.length == 0){
                return classNames;
            }
            for (File childFile : childFiles) {
                if (childFile.isDirectory()){
                    recusionFile(childFile.getAbsolutePath(),classNames,basePath);
                }else {
                    //文件-->类的权限定名称
                    String className = getClassByAbsolutePath(childFile.getAbsolutePath(),basePath);
                    classNames.add(className);
                    System.out.println(childFile.getAbsoluteFile());
                }
            }
        }else {
            //文件 ---> 类的权限定名称
            String className = getClassByAbsolutePath(path,basePath);
            classNames.add(className);
            System.out.println(path);
        }
        return classNames;
    }

    private String getClassByAbsolutePath(String path,String basePath) {
        String fileName = path.substring(path.indexOf(basePath.replaceAll("/","\\\\")))
                .replaceAll("\\\\",".");
        fileName = fileName.substring(0, fileName.indexOf(".class"));
        return fileName;
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    public static void main(String[] args) {
        List<String> allClassName = com.hrpc.hRpcBootStarp.getInstance().getAllClassName("com.hrpc");
        System.out.println(allClassName);
    }

    public hRpcBootStarp group(String master) {
        this.getConfiguration().setGroup(master);
        return this;
    }
}
